package com.getnerator.service.impl;

import com.getnerator.service.GeneratorService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.util.*;

@Service
@RequiredArgsConstructor
public class GeneratorServiceImpl implements GeneratorService {

    private final JdbcTemplate jdbcTemplate;

    @Value("${generator.output.backend}")
    private String backendOut;
    @Value("${generator.output.mapperXml}")
    private String mapperXmlOut;
    @Value("${generator.output.frontend}")
    private String feOut;
    @Value("${generator.basePackage}")
    private String basePackage;
    @Value("${generator.author}")
    private String author;
    @Value("${generator.modulePrefix:generated}")
    private String modulePrefix;

    private Configuration fmConfig;

    private Configuration fm(){
        if (fmConfig == null){
            fmConfig = new Configuration(Configuration.VERSION_2_3_32);
            fmConfig.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "templates");
            fmConfig.setDefaultEncoding("UTF-8");
            fmConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        }
        return fmConfig;
    }

    @Override
    public List<Map<String, Object>> listTables(String keyword) {
        // 确保历史表存在，列表中可标识是否已生成
        ensureHistoryTable();
        return listTablesByJdbc(jdbcTemplate, keyword);
    }

    @Override
    public Map<String, Object> generate(String tableName, boolean force) throws Exception {
        return generateByJdbc(jdbcTemplate, tableName, force);
    }

    private List<Map<String,Object>> buildColumns(String tableName){
        List<Map<String, Object>> cols = jdbcTemplate.queryForList(
                "SELECT COLUMN_NAME AS columnName, DATA_TYPE AS dataType, COLUMN_KEY AS columnKey, IS_NULLABLE AS nullable, COLUMN_COMMENT AS comment FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION",
                tableName);
        List<Map<String,Object>> list = new ArrayList<>();
        for (Map<String,Object> c : cols){
            String column = String.valueOf(c.get("columnName"));
            String dataType = String.valueOf(c.get("dataType"));
            Map<String,Object> m = new HashMap<>();
            m.put("columnName", column);
            m.put("fieldName", toCamel(column, false));
            m.put("javaType", mapJavaType(dataType));
            m.put("comment", c.getOrDefault("comment", ""));
            list.add(m);
        }
        return list;
    }

    private String getTableComment(String tableName){
        try{
            return jdbcTemplate.queryForObject(
                    "SELECT TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?",
                    String.class, tableName);
        }catch (Exception e){
            return "";
        }
    }

    private void renderToFile(String templatePath, Map<String,Object> dataModel, File target) throws Exception{
        target.getParentFile().mkdirs();
        Template t = fm().getTemplate(templatePath);
        try(Writer out = new OutputStreamWriter(Files.newOutputStream(target.toPath()), StandardCharsets.UTF_8)){
            t.process(dataModel, out);
        }
    }

    private String pathForPackage(String pkg){
        return backendOut + "/" + pkg.replace('.', '/');
    }

    private String toCamel(String s, boolean upper){
        StringBuilder r = new StringBuilder();
        for (String p : s.split("[_-]")){
            if (p.isEmpty()) continue;
            r.append(p.substring(0,1).toUpperCase()).append(p.substring(1).toLowerCase());
        }
        String v = r.toString();
        return upper ? v : (v.substring(0,1).toLowerCase()+v.substring(1));
    }
    private String mapJavaType(String mysql){
        switch (mysql){
            case "bigint": return "Long";
            case "int":
            case "tinyint": return "Integer";
            case "decimal": return "java.math.BigDecimal";
            case "datetime":
            case "timestamp":
            case "date": return "java.time.LocalDateTime";
            default: return "String";
        }
    }

    private void ensureHistoryTable(){
        jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS generator_history (table_name varchar(128) PRIMARY KEY, generated_at datetime)");
    }

    private void autoRegisterMenu(String entityName, String lowerName){
        Integer exists = jdbcTemplate.queryForObject(
                "SELECT COUNT(1) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='sys_menu'",
                Integer.class);
        if (exists != null && exists > 0){
            try{
                Integer dup = jdbcTemplate.queryForObject(
                        "SELECT COUNT(1) FROM sys_menu WHERE path=?",
                        Integer.class, "/" + modulePrefix + "/" + lowerName);
                if (dup != null && dup > 0) return;
                jdbcTemplate.update(
                        "INSERT INTO sys_menu (menu_name, path, component, perms, menu_type, visible, status, parent_id, icon, order_num) " +
                                "VALUES (?,?,?,?, 'C', '0','0', 0, 'menu', 999)",
                        entityName, "/" + modulePrefix + "/" + lowerName,
                        "views/generated/" + entityName,
                        modulePrefix + ":" + lowerName + ":list");
            }catch (Exception e){
                writeMenuSqlFallback(entityName, lowerName);
            }
        }else{
            writeMenuSqlFallback(entityName, lowerName);
        }
    }

    private void writeMenuSqlFallback(String entityName, String lowerName){
        try{
            File sqlDir = new File(backendOut).toPath()
                    .resolve("../resources/sql/generated").normalize().toFile();
            sqlDir.mkdirs();
            File f = new File(sqlDir, "menu_" + lowerName + ".sql");
            String sql = "-- 生成器菜单注册脚本（请根据实际 sys_menu 字段名调整后执行）\n" +
                    "-- 名称/路径/组件/权限标识 需与实际表结构对齐\n" +
                    "INSERT INTO sys_menu (menu_name, path, component, perms, menu_type, visible, status, parent_id, icon, order_num)\n" +
                    "VALUES ('" + entityName + "', '/" + modulePrefix + "/" + lowerName + "', 'views/generated/" + entityName + "', '" + modulePrefix + ":" + lowerName + ":list', 'C', '0', '0', 0, 'menu', 999);\n";
            Files.writeString(f.toPath(), sql, StandardCharsets.UTF_8);
        }catch (Exception ignored){ }
    }

    private Map<String, Object> resp(int code, String msg, Object data){
        Map<String, Object> m = new HashMap<>();
        m.put("code", code); m.put("message", msg); m.put("data", data);
        return m;
    }

    // ============== 动态数据源（按指定数据库）支持 ==============
    @Override
    public List<Map<String, Object>> listTablesWithDs(String url, String username, String password, String keyword){
        JdbcTemplate tmp = new JdbcTemplate(new org.springframework.jdbc.datasource.DriverManagerDataSource(url, username, password));
        return listTablesByJdbc(tmp, keyword);
    }

    @Override
    public Map<String, Object> generateWithDs(String url, String username, String password, String tableName, boolean force) throws Exception{
        JdbcTemplate tmp = new JdbcTemplate(new org.springframework.jdbc.datasource.DriverManagerDataSource(url, username, password));
        return generateByJdbc(tmp, tableName, force);
    }

    private List<Map<String, Object>> listTablesByJdbc(JdbcTemplate jt, String keyword){
        String sql = "SELECT t.TABLE_NAME AS tableName, t.TABLE_COMMENT AS tableComment, gh.generated_at AS generatedAt " +
                "FROM information_schema.TABLES t " +
                "LEFT JOIN generator_history gh ON gh.table_name = t.TABLE_NAME " +
                "WHERE t.TABLE_SCHEMA = DATABASE()" +
                (StringUtils.hasText(keyword) ? " AND (t.TABLE_NAME LIKE '%" + keyword + "%' OR t.TABLE_COMMENT LIKE '%" + keyword + "%')" : "") +
                " ORDER BY t.TABLE_NAME";
        return jt.queryForList(sql);
    }

    private Map<String, Object> generateByJdbc(JdbcTemplate jt, String tableName, boolean force) throws Exception{
        // 历史表在默认数据源维护，避免多库重复
        ensureHistoryTable();
        Integer exists = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM generator_history WHERE table_name=?", Integer.class, tableName);
        if (exists != null && exists > 0 && !force) {
            return resp(409, "已生成过，如需覆盖请传 force=true", false);
        }

        String entityName = toCamel(tableName, true);
        String lowerName = toCamel(tableName, false);
        // 目录对齐 admin 架构：controller/service/service.impl/mapper/entity
        String moduleBase = basePackage + ".generated." + lowerName;
        String entityPackage = basePackage + ".entity.generated." + lowerName;
        String mapperPackage = basePackage + ".mapper.generated." + lowerName;
        String servicePackage = basePackage + ".service.generated." + lowerName;
        String serviceImplPackage = basePackage + ".service.impl.generated." + lowerName;
        String controllerPackage = basePackage + ".controller.generated";
        String tableComment = getTableCommentByJdbc(jt, tableName);

        Map<String,Object> model = new HashMap<>();
        model.put("basePackage", basePackage);
        model.put("modulePrefix", modulePrefix);
        model.put("author", author);
        model.put("entityName", entityName);
        model.put("lowerName", lowerName);
        model.put("tableComment", tableComment);
        List<Map<String,Object>> columns = buildColumnsByJdbc(jt, tableName);
        model.put("columns", columns);
        model.put("permissionPrefix", modulePrefix + ":" + lowerName);
        model.put("entityPackage", entityPackage);
        model.put("mapperPackage", mapperPackage);
        model.put("servicePackage", servicePackage);
        model.put("serviceImplPackage", serviceImplPackage);
        model.put("controllerPackage", controllerPackage);

        // 后端
        renderToFile("backend/Entity.java.ftl", model, new File(pathForPackage(entityPackage), entityName + ".java"));
        renderToFile("backend/Mapper.java.ftl", model, new File(pathForPackage(mapperPackage), entityName + "Mapper.java"));
        renderToFile("backend/Service.java.ftl", model, new File(pathForPackage(servicePackage), entityName + "Service.java"));
        renderToFile("backend/ServiceImpl.java.ftl", model, new File(pathForPackage(serviceImplPackage), entityName + "ServiceImpl.java"));
        renderToFile("backend/Controller.java.ftl", model, new File(pathForPackage(controllerPackage), entityName + "Controller.java"));

        // 前端
        renderToFile("frontend/api.ts.ftl", model, new File(feOut + "/api/generated/" + lowerName + ".ts"));
        renderToFile("frontend/page.vue.ftl", model, new File(feOut + "/views/generated/" + entityName + ".vue"));

        jdbcTemplate.update("REPLACE INTO generator_history(table_name, generated_at) VALUES(?,?)", tableName, LocalDateTime.now());

        try { autoRegisterMenu(entityName, lowerName); } catch (Exception ignore) {}
        return resp(200, "生成成功", true);
    }

    private List<Map<String,Object>> buildColumnsByJdbc(JdbcTemplate jt, String tableName){
        List<Map<String, Object>> cols = jt.queryForList(
                "SELECT COLUMN_NAME AS columnName, DATA_TYPE AS dataType, COLUMN_KEY AS columnKey, IS_NULLABLE AS nullable, COLUMN_COMMENT AS comment FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION",
                tableName);
        List<Map<String,Object>> list = new ArrayList<>();
        for (Map<String,Object> c : cols){
            String column = String.valueOf(c.get("columnName"));
            String dataType = String.valueOf(c.get("dataType"));
            Map<String,Object> m = new HashMap<>();
            m.put("columnName", column);
            m.put("fieldName", toCamel(column, false));
            m.put("javaType", mapJavaType(dataType));
            m.put("comment", c.getOrDefault("comment", ""));
            list.add(m);
        }
        return list;
    }

    private String getTableCommentByJdbc(JdbcTemplate jt, String tableName){
        try{
            return jt.queryForObject(
                    "SELECT TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?",
                    String.class, tableName);
        }catch (Exception e){
            return "";
        }
    }
}


